package gqqnbig.jsoup;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedList;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class AdaptiveDownloader
{
	private static final int defaultTimeout = 50000;
	private static final int historySize = 10;

	private final LinkedList<Integer> finishTime = new LinkedList<Integer>();

	public int getTimeout()
	{
		int timeout;
		if (finishTime.size() == 0)
			timeout = defaultTimeout;
		else
		{
			if (finishTime.size() > historySize)
			{
				finishTime.removeFirst();
				assert finishTime.size() == historySize;
			}

			double average = getAverage(finishTime);
			double sd = getStandardDevition(finishTime);
			timeout = (int) (average + 2 * sd);
		}

		return timeout;
	}

	public Document parseWebDocument(String url, int timeoutRetry) throws IOException
	{
		for (int retry = 0; retry < timeoutRetry; retry++)
		{
			int timeout = getTimeout();
			if (System.getProperty("debug", "false").equals("true"))
				System.out.print("Timeout=" + timeout + " ");
			try
			{
				long t = System.currentTimeMillis();
				Document document = Jsoup.parse(new URL(url), timeout);
				t = System.currentTimeMillis() - t;
				finishTime.add((int) t);
				if (System.getProperty("debug", "false").equals("true"))
					System.out.println(", Finish time=" + t);
				return document;
			}
			catch (MalformedURLException e)
			{
				throw new RuntimeException(e);
			}
			catch (SocketTimeoutException e)
			{
				finishTime.add((int) Math.ceil(timeout * 1.5));
				System.out.println(String.format("Failed to download %1s in %2s million seconds. Retry...", url, timeout));

				//@formatter:off
				try{ Thread.sleep(1000 * retry);}
				catch(InterruptedException e2){}
				//@formatter:on
				continue;
			}
		}

		throw new IOException("Failed to download " + url + " after " + timeoutRetry + " times.");
	}

	public static double getAverage(Collection<Integer> array)
	{
		int sum = 0;
		for (Integer integer : array)
			sum += integer;
		return ((double) sum / array.size());
	}

	public static double getStandardDevition(Collection<Integer> array)
	{
		double sum = 0;
		double avg = getAverage(array);
		for (Integer integer : array)
			sum += (integer - avg) * (integer - avg);
		return Math.sqrt(sum / array.size());
	}
}
